#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libgen.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <time.h>
#include <sys/epoll.h>


#define	BACK_LOG	13
#define	MAX_EV		1024
#define	ARR_SIZE(x)	( sizeof(x) / sizeof(x[0]) ) //calculate array size
#define	BUF_SIZE	1024


int StartSocket(int *server_fd, char *listen_addr, int server_port) ;
void print_usage(const char *program_name); 
char cover(char ch) ;

int main(int argc, char **argv)
{
	int			listen_fd ;
	char			*program_name ;
	int			opt = -1 ;
        int			port = 0 ;
	int			daemon_run = 0 ;
	int			ep_fd ;
	struct epoll_event	ep_ev ;
	struct epoll_event	ev_arr[MAX_EV] ;
	int			nready ;
	int			cli_fd,sock_fd ;
	struct sockaddr_in	cli_addr ;
	socklen_t		cli_len ;
	int			i,j ;
	int			rv ;
	char			buf[BUF_SIZE] ;
	char                    wbuf[BUF_SIZE] = "Hello client, Welcome to connect,If you input get_time will get server time ,else will echo upper buf" ;
	time_t			ticks ;

	program_name = basename(argv[0]) ;
	const char *short_opts = "p:hd";  
	const struct option long_opts[] =
	{
		{"help", no_argument, NULL, 'h'},  
		{"port", required_argument, NULL, 'p'},  
		{"daemon", no_argument,NULL, 'd'} ,
		{0, 0, 0, 0}  
	};  
	
	while ((opt= getopt_long(argc, argv, short_opts, long_opts,NULL)) != -1) 
	{
		switch (opt) 
		{
			case 'd':
				daemon_run = 1 ;
				break ;

			case 'p':
				port = atoi(optarg) ;
				break ;

			case 'h':
				print_usage(program_name) ;
				return 0;
		}
	}

	if( !port )
	{
		print_usage(program_name);
		return 0;
	}

	if(daemon_run == 1)
	{
		printf("the process will running in backgrund\n") ;
		daemon(0,0) ;
	}



	program_name = basename(argv[0]) ;

	if( StartSocket(&listen_fd, NULL, port) < 0 ) 
	{
		printf("StartSocket() error: %s\n", strerror(errno) ) ;
		return -1 ;
	}
	printf("StartSocket() sucessful, listen_fd[%d]\n", listen_fd) ;

	ep_fd = epoll_create(1024) ;
	if(ep_fd < 0)
	{
		printf("epoll_create() failed: %s\n", strerror(errno)) ;
		goto cleanup ;
	}
	printf("epoll_create sucessful, epfd[%d]\n", ep_fd) ;
	
	memset(&ep_ev, 0, sizeof(ep_ev)) ;
	ep_ev.data.fd = listen_fd ;
	ep_ev.events = EPOLLIN ;
	if( epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &ep_ev ) < 0)
	{
		printf("epoll_ctl() failed:%s\n", strerror(errno)) ;
	}

	while(1)
	{	/*	timeout set -1 causes epoll_wait() to block indefinitely	*/
		nready = epoll_wait(ep_fd, ev_arr, MAX_EV, -1);
		if(nready == 0)
		{
			printf("epoll_wait timeout!\n") ;
			continue ;
		}
		if(nready < 0)
		{
			printf("epoll_wait failed: %s\n", strerror(errno)) ;
			continue ;
		}
		printf("the %d of file descriptors ready for the requested I/O\n", nready) ;
		
		for(i=0 ; i<nready; i++ )
		{
			if(ev_arr[i].events & EPOLLERR || ev_arr[i].events & EPOLLRDHUP )
			{
				printf("fd[%d] found error event: %s\n",ev_arr[i].data.fd ,strerror(errno)) ;
				epoll_ctl( ep_fd, EPOLL_CTL_DEL ,ev_arr[i].data.fd, NULL ) ;
				close(ev_arr[i].data.fd) ;
			}
			else if(ev_arr[i].data.fd == listen_fd) //listen_fd have event 
			{
				printf("Have client want to connect!\n") ;
				cli_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len) ;
				if(cli_fd < 0) 
				{
					printf("accept client error: %s\n", strerror(errno)) ;
					continue ;
				}
				printf("Accept client[%s:%d] sucessful!\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port)) ;
				ep_ev.data.fd = cli_fd ;
				ep_ev.events= EPOLLIN ;
				if( epoll_ctl(ep_fd, EPOLL_CTL_ADD, cli_fd, &ep_ev) < 0)
				{
					printf("epoll_ctl client[%d] failure: %s, will close client\n", cli_fd, strerror(errno)) ;
					close(cli_fd) ;
					continue ;
				}
				if( write(cli_fd, wbuf, strlen(wbuf)) < 0)
				{
					printf("write to client[%d] failure: %s\n",sock_fd  ,strerror(errno)) ;
					epoll_ctl(ep_fd, EPOLL_CTL_DEL, cli_fd, NULL) ;
					close(cli_fd) ;
					continue ;
				}
			
			}
			else	//Already connect cli_fd have events
			{
				printf("Alread connect client have event come!\n") ;
				memset(buf, 0, sizeof(buf)) ;
				sock_fd = ev_arr[i].data.fd ;
				rv = read(sock_fd, buf, sizeof(buf)) ;
				if(rv <= 0)
				{
					printf("read() error or client disconnect, close client[%d]!\n", sock_fd) ;
					epoll_ctl(ep_fd, EPOLL_CTL_DEL, sock_fd, NULL) ;
					close(sock_fd) ;
					continue ;
				} 
				else
				{
					printf("read %d byte from client[%d]: '%s'\n", rv, sock_fd, buf) ;	
					if(strcmp(buf, "get_time") == 0)
					{
						memset(buf, 0, sizeof(buf)) ;
						ticks = time(NULL) ;
                                                snprintf( buf, sizeof(buf), "%.24s",ctime(&ticks)) ;
						if( write(sock_fd, buf, strlen(buf)) < 0)
						{
							printf("write to client[%d] failure: %s\n",sock_fd  ,strerror(errno)) ;
							epoll_ctl(ep_fd, EPOLL_CTL_DEL, sock_fd, NULL) ;
							close(sock_fd) ;
						}
					}
					else
					{	for(j=0; j<rv; j++)
						{
							buf[j] = cover(buf[j]) ;
						}
						if( write(sock_fd, buf, strlen(buf)) < 0)
						{
							printf("write to client[%d] failure: %s\n",sock_fd  ,strerror(errno)) ;
							epoll_ctl(ep_fd, EPOLL_CTL_DEL, sock_fd, NULL) ;
							close(sock_fd) ;
						}
					}
				}
			}
		}
		

		
	}

	
cleanup:
	close(listen_fd) ;
	close(ep_fd) ;
	
	return 0 ;
}



char cover(char ch)
{
	if(ch>'z'||ch<'a')
	{
		return ch ;
	}
	else
		return (ch - ('a' - 'A') ); 
}

void print_usage(const char *program_name) 
{
	printf("\n%s -- (2018.12.7)\n", program_name);  
	printf(" Usage: %s -p <server_port> [-d]  [-h <server_use_help>]\n", program_name);  
	printf("        -p --port       the server listen port\n") ;
	printf("        -h --help       the server file how to use\n");  
	printf("        -d --daemon     the server running in backgrund\n");  
	return ;
}  


int StartSocket(int *server_fd ,char *listen_addr, int server_port)
{	
	int			fd ;
	struct sockaddr_in	serv_addr ;	
	int			reval ;
	
	if(server_port<1024)
	{
		printf("Set error server_port: Setting the well-known port number!\n") ;
		reval =  -1;
		goto cleanup ;
	}
	
	fd = socket( AF_INET, SOCK_STREAM, 0) ;
	if( fd < 0)
	{
		printf("socket() failed: %s\n", strerror(errno)) ;
		reval = -2 ;
		goto cleanup ;
	}

	memset( &serv_addr, 0 ,sizeof(serv_addr) ) ;
	serv_addr.sin_family = AF_INET ;
	if( !listen_addr )             
	{
		serv_addr.sin_addr.s_addr = htonl(INADDR_ANY) ;		
	}
	else //If addr have value
	{
		serv_addr.sin_addr.s_addr = inet_aton(listen_addr, &serv_addr.sin_addr) ;
	}
	serv_addr.sin_port = htons( server_port) ;
	
	int reuse = 1 ;
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); //reuseaddr

	if( bind( fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr) ) < 0)
	{
		printf("Bind() failed: %s\n", strerror(errno)) ;
		reval = -3 ;
		goto cleanup ;
	}

	if( listen( fd,BACK_LOG) < 0)
	{
		printf("Listen() error: %s\n", strerror(errno)) ;
		reval = -4 ;
		goto cleanup ;
	}
	
cleanup:
	if(reval < 0)
	{	
		close(fd) ;
		return -1 ;
	}
	else
		*server_fd = fd ;

	return 0 ;

}
